%%% ====================================================================
%%% A couple of support functions for Erlsom
%%% 
%%% Copyright (C) 2006 Willem de Jong
%%%
%%% This program is free software; you can redistribute it and/or modify
%%% it under the terms of the GNU General Public License as published by 
%%% the Free Software Foundation; either version 2 of the License, or 
%%% (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful, but
%%% WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License 
%%% along with this program; if not, write to the Free Software 
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
%%% USA
%%%
%%% Author contact: w.a.de.jong@gmail.com
%%% ====================================================================

%%% Version: 0.0.2

-module(erlsom_lib).


-export([makeName/2]).
-export([makeName/4]).
-export([nameWithoutPrefix/1]).
-export([makeAttrName/2]).
-export([makeRef/2]).
-export([makeTag/2]).
-export([makeTag/3]).
-export([findPrefix/2]).
-export([makeType/2]).
-export([translateType/1]).
-export([minMax/1]).
-export([multMinMax/2]).
-export([tagNamespace/2]).
-export([listLength/1]).
-export([toUnicode/1]).
-export([findFile/2]).
-export([findType/2]).
-include("erlsom_compile.hrl").
-include("erlsom.hrl").
-include("erlsom_parse.hrl"). %% the record definitions

%% A place where we will look for import-files.
-define(XSD_IMPORT_PATH, "c:/import").

debug(Text) ->
  io:format("~p\n", [Text]).

%%debug(Text1, Text2) ->
  %%io:format("~p ~p\n", [Text1, Text2]).

minMax(undefined) ->
  1;
minMax("unbounded") ->
  unbound;
minMax(Integer) ->
  list_to_integer(Integer).

multMinMax(0, _) -> 0;
multMinMax(_, 0) -> 0;
multMinMax(unbound, _) -> unbound;
multMinMax(_, unbound) -> unbound;
multMinMax(A, B) -> A * B.

%% returns the URI that belongs to a tag.
%% tag is an atom 'ppp:llll' or 'llll' (ppp = prefix, llll  = local name)
%% namespaces is [#ns{prefix, uri}]
tagNamespace(Tag, Namespaces) ->
  tagNamespace(atom_to_list(Tag), [], Namespaces).

tagNamespace([$: | _Tail], Acc, Namespaces) ->
  case lists:keysearch(lists:reverse(Acc), #ns.prefix, Namespaces) of
    {value, #ns{uri = Uri}} ->
      Uri;
    _Other ->
      undefined
  end;
tagNamespace([], _Acc, _Namespaces) ->
  undefined;
tagNamespace([Char | Tail], Acc, Namespaces) ->
  tagNamespace(Tail, [Char | Acc], Namespaces).
  

nameWithoutPrefix(Name) ->
  nameWithoutPrefix(Name, []).

nameWithoutPrefix([$: | Tail], _Acc) ->
  Tail;
nameWithoutPrefix([Char | Tail], Acc) ->
  nameWithoutPrefix(Tail, [Char | Acc]);
nameWithoutPrefix([], Acc) ->
  lists:reverse(Acc).

translateType(String) ->
  case String of
    "integer" ->
       'integer';
    "int" ->
       'integer';
    "QName" ->
       'qname';
    "boolean" ->
       'bool';
    _Else ->
       'char' 
  end.


findPrefix(undefined, _Namespaces) ->
  [];
findPrefix(Namespace, Namespaces) ->
  case lists:keysearch(Namespace, #ns.uri, Namespaces) of
    {value, #ns{prefix = Prefix}} ->
      Prefix ++ ":";
    _Else ->
      throw({error, "Namespace not found " ++ Namespace})
  end.

makeName(NameInXsd, _ElementFormDefault = "qualified", Path, Prefix) ->
   Prefix ++ Path ++ NameInXsd;

makeName(NameInXsd, _ElementFormDefault, Path, _Prefix) ->
   Path ++ NameInXsd.

%% -record(schemaInfo, {targetNamespace, elementFormDefault, namespacePrefix, namespaces}).
makeName(NameInXsd, #schemaInfo{elementFormDefault="qualified", targetNamespace=TNS, namespaces=NS,
                                path=Path}) ->

  %% find the target namespace in NS,
  %% add the prefix and the path

  case lists:keysearch(TNS, 2, NS) of
    {value, #ns{prefix = Prefix}} ->
      Prefix ++ ":" ++ Path ++ NameInXsd;
    _Else ->
      if 
        TNS == undefined ->
          NameInXsd;
        true ->
          throw({error, "Namespace not found " ++ NameInXsd})
      end
  end;

makeName(NameInXsd, #schemaInfo{targetNamespace=TNS, namespaces=NS, path=[]}) ->
  %% elementFormDefault = unqualified (or undefined)
  %% since Path = [], this is a global element,and we need to add the prefix.

  case lists:keysearch(TNS, 2, NS) of
    {value, #ns{prefix = Prefix}} ->
      Prefix ++ ":" ++ NameInXsd;
    _Else ->
      if 
        TNS == undefined ->
          NameInXsd;
        true ->
          throw({error, "Namespace not found " ++ NameInXsd})
      end
  end;

makeName(NameInXsd, #schemaInfo{targetNamespace=TNS, namespaces=NS, path=Path}) ->
  %% elementFormDefault = unqualified (or undefined)
  %% since Path /= [], this is a local element,and we need to add the prefix and the path.

  case lists:keysearch(TNS, 2, NS) of
    {value, #ns{prefix = Prefix}} ->
      Prefix ++ ":" ++ Path ++ NameInXsd;
    _Else ->
      if 
        TNS == undefined ->
          Path ++ NameInXsd;
        true ->
          throw({error, "Namespace not found " ++ NameInXsd})
      end
  end.


%% -record(schemaInfo, {targetNamespace, elementFormDefault, namespacePrefix, namespaces}).
makeAttrName(NameInXsd, _Info) ->
  NameInXsd.


makeType(undefined, _) ->
   {'#PCDATA', 'char'};
  
makeType(#qname{uri = NS, localPart = Local}, Namespaces) ->
  case NS of
    "http://www.w3.org/2001/XMLSchema" ->
      {'#PCDATA', translateType(Local)};
    _Else ->
      %% TODO: replace by a call to makeRef
      case lists:keysearch(NS, 2, Namespaces) of
        {value, #ns{prefix = Prefix}} ->
          Prefix ++ ":" ++ Local;
        _ ->
          if 
            NS == undefined ->
              Local;
            true ->
              throw({error, "Namespace not found " ++ NS})
          end
      end
  end.

%% makeRef(#qname{localPart = LocalName, mappedPrefix = Prefix}) ->
  %% case Prefix of
    %% undefined ->
      %% LocalName;
    %% _Else ->
      %% Prefix ++ ":" ++ LocalName
  %% end.
makeRef(#qname{uri = "http://www.w3.org/XML/1998/namespace", 
                localPart = "lang"}, _Namespaces) ->
  %% weird case
  "xml:lang";


makeRef(#qname{uri = NS, localPart = Local}, Namespaces) ->
  case lists:keysearch(NS, 2, Namespaces) of
    {value, #ns{prefix = Prefix}} ->
      Prefix ++ ":" ++ Local;
    _ ->
      if 
        NS == undefined ->
          Local;
        true ->
          debug(Local),
          throw({error, "Namespace not found " ++ NS})
      end
  end.
  %% case Prefix of
    %% undefined ->
      %% LocalName;
    %% _Else ->
      %% Prefix ++ ":" ++ LocalName
  %% end.



makeTag(NameInXsd, Prefix, _ElementFormDefault = "qualified") ->
      Prefix ++ NameInXsd;
makeTag(NameInXsd, _Prefix, _ElementFormDefault) ->
      NameInXsd.

makeTag(NameInXsd, #schemaInfo{targetNamespace=undefined}) ->
  NameInXsd;

makeTag(NameInXsd, #schemaInfo{elementFormDefault="qualified", targetNamespace=TNS, namespaces=NS}) ->
  %% find the target namespace in NS,
  %% add the prefix.
  case lists:keysearch(TNS, 2, NS) of
    {value, #ns{prefix = Prefix}} ->
      Prefix ++ ":" ++ NameInXsd;
    _Else ->
      if 
        TNS == undefined ->
          NameInXsd;
        true ->
          throw({error, "Namespace not found " ++ TNS})
      end
  end;

makeTag(NameInXsd, _SchemaInfo) ->
  %% defaultElementForm = unqualified, do not add the prefix (TODO: exept for global elements and types).
  NameInXsd.

listLength(undefined) -> 0;
listLength(List) -> length(List).

toUnicode(Bin) ->
  autodetect(Bin).

findType(TypeReference, Types) ->
  case lists:keysearch(TypeReference, #type.nm, Types) of
    {value, Value} ->
      Value;
    _Else ->
      throw({error, "Type not found: " ++ atom_to_list(TypeReference)})
  end.

%%------------------------------------------------------------------------------
%% This was copied from xmerl_lib (by Ulf Wiger), but modified to work
%% on binaries in stead of lists. I also removed the option to specify
%% a character set - the function only looks at the first 2 or 4 bytes.
%% 
%% Auto detect what kind of character set we are dealing with and transform
%% to Erlang integer Unicode format if found.
%% Appendix F, Page 56-57, XML 1.0 W3C Recommendation 6 October 2000
%% (http://www.w3.org/TR/REC-xml)
%% 00 00 00 3C ( "<" in UCS-4 big-endian)
%% 3C 00 00 00 ( "<" in UCS-4 little-endian)
%% FE FF (UTF-16 - big-endian Mark)
%% FF FE (UTF-16 - little-endian Mark)
%% 00 3C 00 3F ( "<?" in UTF-16 big-endian)
%% 3C 00 3F 00 ( "<?" in UTF-16 big-endian)
%% 3C 3F (7-bit,8-bit or mixed width encoding)
%% 4C 6F A7 94 (EBCDIC) - Not Implemented!!!!

%% Check byte-order mark and transform to Unicode, Erlang integer
%%% --- With byte-order mark
autodetect(<<0,0,16#fe,16#ff, Input/binary>>) ->
    %% debug("ucs4be"),
    ucs:from_ucs4be(Input);
autodetect(<<16#ff,16#fe,0,0, Input/binary>>) ->
    %% debug("ucs4le"),
    ucs:from_ucs4le(Input);

autodetect(<<16#fe,16#ff, Input/binary>>) ->
    %% debug("utf16be"),
    ucs:from_utf16be(Input);
autodetect(<<16#ff,16#fe, Input/binary>>) ->
    %% debug("utf16le"),
    ucs:from_utf16le(Input);

autodetect(<<16#ef,16#bb,16#bf, Input/binary>>) ->
    %% debug("utf8"),
    ucs:from_utf8(Input);

%%% --- Without byte-order mark
autodetect(Input = <<0,0,0,16#3c, _Rest/binary>>) ->
    %% debug("ucs4be no byte-order mark"),
    ucs:from_ucs4be(Input);
autodetect(Input = <<16#3c,0,0,0, _Rest/binary>>) ->
    %% debug("ucs4le no byte-order mark"),
    ucs:from_ucs4le(Input);

autodetect(Input = <<0,16#3c,0,16#3f, _Rest/binary>>) ->
    %% debug("utf16be no byte-order mark"),
    ucs:from_utf16be(Input);
autodetect(Input = <<16#3c,0,16#3f,0, _Rest/binary>>) ->
    %% debug("utf16le no byte-order mark"),
    ucs:from_utf16le(Input);

autodetect(Content) ->
    %% debug("unknown encoding? Assume UTF-8"),
    ucs:from_utf8(Content).

%% function to find imported files. Should probably be replaced by something more advanced.
findFile(_Namespace, Location) ->
  %% if 'Location' is a file, then we're done.
  case filelib:is_file(Location) of
    true -> {ok, Location};
    _ -> 
      %% try IMPORT_PATH ++ Location
      Attempt2 = filename:join([?XSD_IMPORT_PATH, Location]),
      case filelib:is_file(Attempt2) of
        true -> {ok, Attempt2};
        _ -> 
          %% try IMPORT_PATH ++ the last bit (the filename) of location.
          Attempt3 = filename:join([?XSD_IMPORT_PATH, filename:basename(Location)]),
          case filelib:is_file(Attempt3) of
            true -> {ok, Attempt3};
            _ -> 
              not_found
          end
      end
  end.


  
-define(IMPORT_PATH, "c:/import").
  
